package cn.school.acsys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import cn.school.acsys.conf.AccessToken;
import cn.school.acsys.entity.*;
import cn.school.acsys.service.AccessControlSysService;
import cn.school.acsys.vo.AcsFlowQueryVo;
import cn.school.common.api.CommonPage;
import cn.school.common.utils.StringUtils;
import cn.school.student.dao.StudentInfoDao;
import cn.school.student.entity.StudentInfo;
import cn.school.student.request.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @className: AccessControlSysServiceImpl
 * @author: Alex
 * @date: 2023/3/14
 **/
@Slf4j
@Service
public class AccessControlSysServiceImpl implements AccessControlSysService {
	
	@Autowired
	private StudentInfoDao studentInfoDao;
	
	@Autowired
	RedisTemplate<String, Object> redisTemplate;
	

	private String IP_STRING;
	

	private String DEVICE_APP_ID;
	

	private String DEVICE_APP_SECRET;
	
	// 门禁设备号

	private String DEVICE_NUMS;
	
	private static String ACCESS_TOKEN;
	
	private static final String ACCTOKEN_KEY = "access.control.sys.token.key";
	
	
	
	@Override
	public List<AcsSysInfo> getAccessControlFlowInfo(String startDate) {
		// 查询access_token
		getAccessControlSysToken();
		
		// 查询门禁流水
		List<AcsFlowResp> acsFlowResps = getAccountFlow(startDate);
		if (CollectionUtil.isEmpty(acsFlowResps)) {
			return Collections.emptyList();
		}
		
		// 门禁流水可能有多条,需要过滤掉重复的
		List<AcsFlowResp> newAcs = new ArrayList<>();
		AcsFlowFilter(acsFlowResps, newAcs);
		
		// 通过accNum查询个人账号信息 找到身份证号
		List<String> cardNos = matchIdCardNoByAccNum(newAcs);
		
		// List<String> collect = listMap.keySet().stream().collect(Collectors.toList());
		
		if (CollectionUtil.isEmpty(cardNos)) {
			return Collections.emptyList();
		}
		// 查询家长电话 -- 根据身份证id
		List<StudentInfo> studentInfoList = getStudentsInfo(cardNos);
		Map<String, List<StudentInfo>> studentMap = studentInfoList.stream().collect(Collectors.groupingBy(StudentInfo::getCertificateId));
		
		List<AcsSysInfo> acsSysInfos = new ArrayList<>();
		
		List<AcsFlowResp> stuInOrOutInfos = new ArrayList<>();
		
		for (AcsFlowResp newAc : newAcs) {
			List<StudentInfo> infos = studentMap.get(newAc.getPercode());
			if (CollectionUtil.isEmpty(infos)) {
				continue;
			}
			StudentInfo studentInfo = infos.get(0);
			// 构造门禁系统信息数据，用于构造通知
			AcsSysInfo acsSysInfo = buildAcsSysInfo(newAc, studentInfo);
			stuInOrOutInfos.add(newAc);
			acsSysInfos.add(acsSysInfo);
		}
		
		if (CollectionUtil.isNotEmpty(studentInfoList)) {
			stuInOrOutInfos
					.forEach(stuInOrOutInfo -> log.info(String.format("门禁流水记录，姓名:%s,身份证:%s,进出时间:%s,出入口:%s"
							, stuInOrOutInfo.getAccName(), stuInOrOutInfo.getPercode(), stuInOrOutInfo.getOcurrDateTime(), stuInOrOutInfo.getDeviceName())));
		}
		
		return acsSysInfos;
	}
	
	@NotNull
	private AcsSysInfo buildAcsSysInfo(AcsFlowResp newAc, StudentInfo studentInfo) {
		AcsSysInfo acsSysInfo = new AcsSysInfo();
		acsSysInfo.setStudentClass(String.valueOf(studentInfo.getClassId()));
		acsSysInfo.setCertificatedId(studentInfo.getCertificateId());
		acsSysInfo.setStudentName(studentInfo.getName());
		acsSysInfo.setGuardianPhoneNum(studentInfo.getGuardianPhoneNumber());
		acsSysInfo.setStudentPhoneNum(studentInfo.getPhone());
		acsSysInfo.setStRegistrationId(studentInfo.getStRegistrationId());
		acsSysInfo.setGuRegistrationId(studentInfo.getGuRegistrationId());
		acsSysInfo.setOcurrDateTime(newAc.getOcurrDateTime());
		acsSysInfo.setInOrOutName("1");
		if (newAc.getDeviceName().contains("出口")) {
			acsSysInfo.setInOrOutName("0");
		}
		return acsSysInfo;
	}
	
	/**
	 * 将门禁流水中percode的数据转换成身份证号码， 通过accNum查询账号信息接口去匹配
	 *
	 * @author alex
     * @param: newAcs
	 * @date 2023/3/22 10:56
	 * @return
	 */
	private List<String> matchIdCardNoByAccNum(List<AcsFlowResp> newAcs) {
		Set<String> collect = newAcs.stream().map(AcsFlowResp::getAccNum).collect(Collectors.toSet());
		// 调用查询账号信息接口
		AccountMessage accountMessage = new AccountMessage();
		accountMessage.setEpId("1");
		accountMessage.setAreaNums("1");
		accountMessage.setAccNums(CollectionUtil.join(collect, ","));
		String sign = getSignString(accountMessage);
		accountMessage.setSign(sign);
		
		List<AccMessageResp> accMessageResps = getAccountMessage(accountMessage);
		if (CollectionUtil.isEmpty(accMessageResps)) {
			return Collections.emptyList();
		}
		
		for (AcsFlowResp newAc : newAcs) {
			accMessageResps.stream()
					.filter(accMessage -> Objects.equals(newAc.getAccNum(), accMessage.getAccNum()))
					.findAny()
					.ifPresent(accMessageResp -> newAc.setPercode(accMessageResp.getIdNo()));
		}
		
		Set<String> idNos = newAcs.stream().map(AcsFlowResp::getPercode).collect(Collectors.toSet());
		return ListUtil.toList(idNos);
	}
	
	
	@Override
	public CommonPage<AcsFlowResp> getAcsFlowList(AcsFlowQueryVo vo) {
		getAccessControlSysToken();
		AccEventRecord accEventRecord = buildQueryAccessRecord(vo);
		
		JSONObject resultObject = queryAcceventRecordForHttp(accEventRecord);
		if (Objects.isNull(resultObject)) {
			return CommonPage.restPage(new Page<>());
		}
		
		String allData = JSON.toJSONString(resultObject.get("data"));
		log.info("请求门禁获取门禁流水：{}",allData);
		JSONObject jsonObject = JSON.parseObject(allData);
		
		List<AcsFlowResp> list = JSON.parseArray(JSON.toJSONString(jsonObject.get("list")), AcsFlowResp.class);
		// 匹配身份证号码
		matchIdCardNoByAccNum(list);
		
		IPage<AcsFlowResp> acsFlowRespPage = new Page<>();
		acsFlowRespPage.setTotal(Long.parseLong(String.valueOf(jsonObject.get("count"))));
		acsFlowRespPage.setSize(vo.getPageSize());
		acsFlowRespPage.setCurrent(vo.getPageNum());
		acsFlowRespPage.setRecords(list);
		return CommonPage.restPage(acsFlowRespPage);
	}
	
	@Override
	public List<Map<String, String>> getAccNumsForAccName(String accName) {
		getAccessControlSysToken();
		// 根据传入的是否是身份证号还是名字构造请求对象
		AccountMessage accountMessage = buildAccountMessageByNameOrId(accName);
		
		String sign = getSignString(accountMessage);
		accountMessage.setSign(sign);
		
		List<AccMessageResp> accMessageResps = getAccountMessage(accountMessage);
		
		if (CollectionUtil.isEmpty(accMessageResps)) {
			return Collections.emptyList();
		}
		List<Map<String, String>> result = new ArrayList<>();
		for (AccMessageResp accMessageResp : accMessageResps) {
			Map<String, String> map = new HashMap<>();
			map.put("accNum", accMessageResp.getAccNum());
			map.put("accName", accMessageResp.getAccName());
			map.put("percode", accMessageResp.getIdNo());
			result.add(map);
		}
		
		return result;
	}
	
	private AccountMessage buildAccountMessageByNameOrId(String accName) {
		if (org.apache.commons.lang3.StringUtils.isNumeric(accName)) {
			// 构造身份证请求对象
			return new AccountMessage("1", "1", "3", "1",  accName);
		}
		return new AccountMessage("1", "1", accName);
	}
	
	@NotNull
	private AccEventRecord buildQueryAccessRecord(AcsFlowQueryVo vo) {
		
		AccEventRecord accEventRecord = new AccEventRecord();
		accEventRecord.setStart(String.valueOf(vo.getPageNum()));
		if (StringUtils.isNotEmpty(vo.getAccNum())) {
			accEventRecord.setAccNum(vo.getAccNum());
		}
		accEventRecord.setLimit(String.valueOf(vo.getPageSize()));
		accEventRecord.setOrderType("1"); // 降序
		accEventRecord.setDeviceNums(DEVICE_NUMS);  // 门禁设备号
		accEventRecord.setStartTime(DateUtil.formatDateTime(DateUtil.offsetDay(new Date(), -1)));
		accEventRecord.setEndTime(DateUtil.formatDateTime(new Date()));
		
		if (Objects.nonNull(vo.getStartTime()) && Objects.nonNull(vo.getEndTime())) {
			accEventRecord.setStartTime(vo.getStartTime());
			accEventRecord.setEndTime(vo.getEndTime());
		}
		
		String signString = getSignString(accEventRecord);
		accEventRecord.setSign(signString);
		return accEventRecord;
	}
	
	@NotNull
	private void AcsFlowFilter(List<AcsFlowResp> acsFlowResps, List<AcsFlowResp> newAcs) {
		List<AcsFlowResp> collect = acsFlowResps.stream().filter(acsFlowResp -> acsFlowResp.getEventName().contains("正常开门")).collect(Collectors.toList());
		Map<String, List<AcsFlowResp>> listMap = collect.stream().collect(Collectors.groupingBy(AcsFlowResp::getAccNum));
		for (String s : listMap.keySet()) {
			List<AcsFlowResp> flowResps = listMap.get(s);
			if (flowResps.size() == 1) {
				newAcs.addAll(flowResps);
				continue;
			}
			for (int i = 0; i < flowResps.size() - 1; i++) {
				for (int j = flowResps.size() - 1; j > i; j--) {
					// 相同出入方向且时间间隔在10s以内的数据过滤掉
					if (Objects.equals(flowResps.get(j).getDeviceName(), flowResps.get(i).getDeviceName())
					&& timeDifference(flowResps.get(j).getOcurrDateTime(), flowResps.get(i).getOcurrDateTime()) < 10) {
						flowResps.remove(j);
					}
				}
			}
			newAcs.addAll(listMap.get(s));
		}
	}
	
	private long timeDifference(String date1, String date2) {
		DateTime time1 = DateUtil.parse(date1, "yyyy/MM/dd HH:mm:ss");
		DateTime time2 = DateUtil.parse(date2, "yyyy/MM/dd HH:mm:ss");
		
		return Math.abs(time1.getTime() - time2.getTime())/1000;
	}
	
	private void getAccessControlSysToken() {
		if (Boolean.FALSE.equals(redisTemplate.hasKey(ACCTOKEN_KEY))) {
			JSONObject jsonObject = getAccessToken(DEVICE_APP_ID, DEVICE_APP_SECRET);
			ACCESS_TOKEN = jsonObject.getString("access_token");
			AccessToken accessObject = new AccessToken();
			accessObject.setAccessToken(ACCESS_TOKEN);
			accessObject.setExpiryTime(String.valueOf(jsonObject.getLong("expires_in")));
			redisTemplate.opsForValue().set(ACCTOKEN_KEY, JSON.toJSONString(accessObject), 2, TimeUnit.HOURS);
			return;
		}
		Object object = redisTemplate.opsForValue().get(ACCTOKEN_KEY);
		String jsonStr = JSONUtil.toJsonStr(object);
		JSONObject jsonObject = JSONObject.parseObject(jsonStr);
		AccessToken accessToken = new AccessToken();
		accessToken.setAccessToken(String.valueOf(jsonObject.get("accessToken")));
		accessToken.setExpiryTime(String.valueOf(jsonObject.get("expiryTime")));
		ACCESS_TOKEN = accessToken.getAccessToken();
		//过期时间的毫秒数 如果为0则不过期 如果不为0则提前两分钟提前刷新token
		if (System.currentTimeMillis()/1000 >= Long.parseLong(accessToken.getExpiryTime()) && !Objects.equals(accessToken.getExpiryTime(), "0")) {
			// 获取新的token
			JSONObject newJsonObject = getAccessToken(DEVICE_APP_ID, DEVICE_APP_SECRET);
			AccessToken accessObject = new AccessToken();
			accessObject.setAccessToken(newJsonObject.getString("access_token"));
			accessObject.setExpiryTime(newJsonObject.getString("expires_in"));
			if (!Objects.equals(accessObject.getExpiryTime(), "0")) {
				accessObject.setExpiryTime(String.valueOf(Long.parseLong(accessToken.getExpiryTime())  + System.currentTimeMillis()/1000 - 1000 * 60 * 2L));
			}
			redisTemplate.opsForValue().set(ACCTOKEN_KEY, JSON.toJSONString(accessObject),2, TimeUnit.HOURS);
		}
	}
	
	private JSONObject getAccessToken(String deviceAppId, String deviceAppSecret) {
		String urlStr = IP_STRING + "api/token";
		String resultStr = HttpRequest.sendGet(urlStr, "appid=" + deviceAppId + "&appsecret=" + deviceAppSecret);
		return JSONObject.parseObject(resultStr);
	}
	
	private List<StudentInfo> getStudentsInfo(List<String> certificateIds) {
		LambdaQueryWrapper<StudentInfo> queryWrapper = new LambdaQueryWrapper<>();
		queryWrapper.in(StudentInfo::getCertificateId, certificateIds);
		return studentInfoDao.selectList(queryWrapper);
	}
	
	/**
	 * 查询账户信息 (暂时未使用)
	 *
	 * @return
	 * @author alex
	 * @date 2023/3/15 16:34
	 */
	private void getAccountInfo() {
		AccountInfo accountInfo = new AccountInfo();
		accountInfo.setQueryType("2");
		accountInfo.setUniqueId("20170901");
		
		String signString = getSignString(accountInfo);
		accountInfo.setSign(signString);
		
		String urlStr = IP_STRING + "api/common/infoqueryservice/getaccount?access_token=" + ACCESS_TOKEN;
		
		String result = HttpRequest.postWithParamsForString(urlStr, BeanUtil.beanToMap(accountInfo));
	}
	
	/**
	 * 查询单个账户信息
	 *
	 * @return
	 * @author alex
	 * @date 2023/3/15 16:34
	 */
	private List<AccMessageResp> getAccountMessage(AccountMessage accountMessage) {
		JSONObject object = queryAccountMessForHttp(accountMessage);
		if (Objects.isNull(object)) {
			return Collections.emptyList();
		}
		
		String data = JSON.toJSONString(object.get("data"));
		JSONObject jsonObject = JSON.parseObject(data);
		List<AccMessageResp> list = JSON.parseArray(JSON.toJSONString(jsonObject.get("list")), AccMessageResp.class);
		
		if (CollectionUtil.isEmpty(list)) {
			return Collections.emptyList();
		}
		return list;
	}
	
	private JSONObject queryAccountMessForHttp(AccountMessage accountMessage) {
		String urlStr = IP_STRING + "api/common/queryaccountinfo?access_token=" + ACCESS_TOKEN;
		
		String result = HttpRequest.postWithParamsForString(urlStr, BeanUtil.beanToMap(accountMessage));
		if (StringUtils.isEmpty(result)) {
			log.error("请求账号信息失败，返回为空！");
			return null;
		}
		
		JSONObject object = JSON.parseObject(result);
		if (!Objects.equals(object.get("code"), "0")) {
			log.error(String.format("请求账号信息失败，code：%s 错误信息：%s", object.get("code"), object.get("msg")));
			return null;
		}
		return object;
	}
	
	/**
	 * 查询门禁流水
	 *
	 * @return
	 * @author alex
	 * @date 2023/3/15 16:37
	 */
	private List<AcsFlowResp> getAccountFlow(String startDate) {
		// 构造请求数据
		AccEventRecord accEventRecord = buildAccEventRecord(startDate);
		log.info("请求门禁数据参数：{}",JSON.toJSONString(accEventRecord));
		// 第一次请求查询出分页的流水 然后获取总条数
		JSONObject object = queryAcceventRecordForHttp(accEventRecord);
		if (Objects.isNull(object)) {
			return Collections.emptyList();
		}
		
		String data = JSON.toJSONString(object.get("data"));
		String count = String.valueOf(JSON.parseObject(data).get("count"));
		accEventRecord.setLimit(String.valueOf(count));
		String signString = getSignString(accEventRecord);
		accEventRecord.setSign(signString);
		
		// 第二次查询出所有的门禁流水
		JSONObject resultObject = queryAcceventRecordForHttp(accEventRecord);
		if (Objects.isNull(resultObject)) {
			return Collections.emptyList();
		}
		
		String allData = JSON.toJSONString(resultObject.get("data"));
		log.info("请求门禁获取门禁流水：{}",allData);
		JSONObject jsonObject = JSON.parseObject(allData);
		
		return JSON.parseArray(JSON.toJSONString(jsonObject.get("list")), AcsFlowResp.class);
	}
	
	private JSONObject queryAcceventRecordForHttp(AccEventRecord accEventRecord) {
		String urlStr = IP_STRING + "api/common/infoqueryservice/queryacceventrecord?access_token=" + ACCESS_TOKEN;
		String result = HttpRequest.postWithParamsForString(urlStr, BeanUtil.beanToMap(accEventRecord));
		if (StringUtils.isEmpty(result)) {
			log.error("请求门禁流水失败，返回为空！");
			return null;
		}
		
		JSONObject object = JSON.parseObject(result);
		if (!Objects.equals(object.get("code"), "0")) {
			log.error(String.format("请求门禁流水失败，code：%s 错误信息：%s", object.get("code"), object.get("msg")));
			if (Objects.equals(object.get("code"), "40004")) {
				redisTemplate.delete(ACCTOKEN_KEY);
			}
			return null;
		}
		return object;
	}
	
	@NotNull
	private AccEventRecord buildAccEventRecord(String startDate) {
		AccEventRecord accEventRecord = new AccEventRecord();
		accEventRecord.setStart("1");
		accEventRecord.setLimit("2");
		accEventRecord.setOrderType("1"); // 降序
		accEventRecord.setDeviceNums(DEVICE_NUMS);  // 门禁设备号
		accEventRecord.setStartTime(startDate);
		accEventRecord.setEndTime(DateUtil.formatDateTime(new Date()));
		
		String signString = getSignString(accEventRecord);
		accEventRecord.setSign(signString);
		return accEventRecord;
	}
	
	private AccountMessage buildAccount() {
		AccountMessage accountMessage = new AccountMessage();
		accountMessage.setEpId("1");
		accountMessage.setAreaNums("1");
		accountMessage.setQueryType("1");
		return accountMessage;
	}
	
	/**
	 * 构造请求数据签名
	 *
	 * @param object
	 * @return string
	 * @author alex
	 * @date 2023/3/15
	 */
	private <T> String getSignString(T object) {
		Map<String, Object> objectMap = BeanUtil.beanToMap(object);
		
		return encodeSign(objectMap, DEVICE_APP_ID);
	}
	
	public String encodeSign(Map<String, Object> map, String key) {
		if (StringUtils.isEmpty(key)) {
			throw new RuntimeException("签名key不能为空");
		}
		
		SortedMap<String, Object> sortedMap = new TreeMap<String, Object>(map);
		
		Set<Map.Entry<String, Object>> entries = sortedMap.entrySet();
		Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
		List<String> values = new ArrayList<>();
		
		while (iterator.hasNext()) {
			Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next();
			String k = String.valueOf(entry.getKey());
			String v = "";
			if (Objects.nonNull(entry.getValue())) {
				v = String.valueOf(entry.getValue());
			}
			if (!"sign".equals(k) && !"key".equals(k)) {
				values.add(k + "=" + v);
			}
		}
		values.add("key=" + key);
		String sign = StringUtils.join("&", values);
		return encodeByMD5(sign).toUpperCase();
	}
	
	/**
	 * 通过MD5加密
	 *
	 * @param algorithmStr
	 * @return String
	 * @author alex
	 */
	public String encodeByMD5(String algorithmStr) {
		if (algorithmStr == null) {
			return null;
		}
		try {
			MessageDigest messageDigest = MessageDigest.getInstance("md5");
			messageDigest.update(algorithmStr.getBytes("utf-8"));
			return getFormattedText(messageDigest.digest());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
	}
	
	private String getFormattedText(byte[] digest) {
		StringBuffer buffer = new StringBuffer();
		// 把每一个byte，做一个与运算，0xff
		for (byte b : digest) {
			// 加盐
			int number = b & 0xff;
			String str = Integer.toHexString(number);
			if (str.length() == 1) {
				buffer.append("0");
			}
			buffer.append(str);
		}
		
		return buffer.toString();
	}
	
}
